#include <ka.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <sys/time.h>

uint64_t getTimeMS()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (tv.tv_sec * 1000 + tv.tv_usec / 1000);
}

void bin2hex(uint8_t *bin, size_t bin_len, char *hex)
{
    for (size_t i = 0; i < bin_len; i++)
    {
        sprintf(hex + 2 * i, "%02x", bin[i]);
    }
    hex[bin_len * 2] = '\0';
}

size_t hex2bin(char *hex, uint8_t *bin)
{
    size_t hex_len = strlen(hex);
    printf("hex_len:%ld\n", hex_len);
    if (hex_len % 2 != 0)
    {
        return 0;
    }
    for (size_t i = 0; i < hex_len; i += 2)
    {
        sscanf(hex + i, "%2hhx", &bin[i / 2]);
    }
    return hex_len / 2;
}

void print_bin(uint8_t *bin, size_t bin_len)
{
    for (size_t i = 0; i < bin_len; i++)
    {
        printf("%02x", bin[i]);
    }
    printf("\n");
}

int option_add(char *args[])
{
    return ka_add_guest(args[0]);
}

int option_delete(char *args[])
{
    return ka_delete_guest(args[0]);
}

int option_host_encrypt(char *args[])
{
    printf("host encrypt idata [%s]\n", args[0]);
    uint8_t *idata = (uint8_t*)args[0];
    int ilen = strlen((char*)idata);
    uint8_t odata[1024];
    int olen = sizeof(odata);
    int rc = ka_host_encrypt(idata, ilen, odata, &olen);
    printf("olen : %d\n", olen);
    if (rc)
        return rc;
    printf("host encrypt odata : ");
    print_bin(odata, olen);
    return 0;
}

int option_guest_encrypt(char *args[])
{
    printf("guest encrypt idata [%s]\n", args[1]);
    char *host_path = args[0];
    uint8_t *idata = (uint8_t*)args[1];
    int ilen = strlen((char*)idata);
    uint8_t odata[1024];
    int olen = sizeof(odata);
    int rc = ka_guest_encrypt(host_path, idata, ilen, odata, &olen);
    if (rc)
        return rc;
    printf("guest encrypt odata : ");
    print_bin(odata, olen);
    return 0;
}

int option_host_decrypt(char *args[])
{
    printf("host decrypt idata [%s]\n", args[0]);
    uint8_t idata[1024];
    int ilen = hex2bin(args[0], idata);
    uint8_t odata[1024];
    int olen = sizeof(odata);
    int rc = ka_host_decrypt(idata, ilen, odata, &olen);
    if (rc)
        return rc;
    odata[olen] = '\0';
    printf("host decrypt odata : [%s]\n", odata);
    return 0;
}

int option_guest_decrypt(char *args[])
{
    printf("host decrypt idata [%s]\n", args[1]);
    char *host_path = args[0];
    uint8_t idata[1024];
    int ilen = hex2bin(args[1], idata);
    uint8_t odata[1024];
    int olen = sizeof(odata);
    int rc = ka_guest_decrypt(host_path, idata, ilen, odata, &olen);
    if (rc)
        return rc;
    odata[olen] = '\0';
    printf("host decrypt odata : [%s]\n", odata);
    return 0;
}

#define pipe_test_cnt (sizeof(pipe_test_size) / sizeof(pipe_test_size[0]))
#define test_loop 16
int pipe_test_size[8192/64] = {};
uint8_t pipe_test_buffer[8192];

int option_host_pipe_test(char *args[])
{
    char *pipe = args[0];
    printf("Pipe file %s\n", pipe);
    for (size_t i = 0; i < sizeof(pipe_test_buffer); i++)
    {
        pipe_test_buffer[i] = 0xCE;
    }
    printf("mkfifo\n");
    mkfifo(pipe, 0644);
    ka_add_guest(pipe);
    int fd = open(pipe, O_WRONLY);
    uint8_t buffer[16384];
    int buffer_len;
    printf("KA FIFO Test list [loop-%d]:",test_loop);
    for (size_t i = 0; i < pipe_test_cnt; i++)
    {
        pipe_test_size[i] = i * 64;
        printf(" [%d]", pipe_test_size[i]);
    }
    printf("\n");

    for (size_t i = 0; i < pipe_test_cnt; i++)
    {

        printf("KA FIFO Test : size [%d]\n", pipe_test_size[i]);
        uint64_t begin_time = getTimeMS();
        for (size_t j = 0; j < test_loop; j++)
        {
            // printf("test loop cnt %ld\r", j);
            // fflush(stdout);
            // write(fd, pipe_test_buffer, pipe_test_size[i]);
            // continue;
            buffer_len = sizeof(buffer);
            int rc = ka_host_encrypt(pipe_test_buffer, pipe_test_size[i], buffer, &buffer_len);
            assert(rc == 0);
            write(fd, buffer, buffer_len);
        }
        uint64_t end_time = getTimeMS();
        printf("Test size[%d] used time %ld ms\n", pipe_test_size[i], end_time - begin_time);
    }

    return 0;
}
int option_guest_pipe_test(char *args[])
{
    char *pipe = args[0];
    char *host = args[1];
    printf("Pipe file %s\n", pipe);
    printf("Host file %s\n", host);

    int fd = open(pipe, O_RDONLY);

    uint8_t buffer[8192];
    uint8_t decrypt_buffer[sizeof(buffer)] = {0};
    while (1)
    {
        int n = read(fd, buffer, sizeof(buffer));
        if (n <= 0)
            continue;
        // for (size_t i = 0; i < n; i++)
        // {
        //     assert(buffer[i] == 0xCE);
        // }
        // continue;

        assert(n % 128 == 0);
        int decrypt_buffer_len = sizeof(decrypt_buffer);
        // printf("ka_guest_decrypt\n");
        int rc = ka_guest_decrypt(host, buffer, n, decrypt_buffer, &decrypt_buffer_len);
        // printf("Recv %d\n", decrypt_buffer_len);
        if (rc)
        {
            printf("rc %x\n", rc);
        }
        for (size_t i = 0; i < decrypt_buffer_len; i++)
        {
            assert(decrypt_buffer[i] == 0xCE);
        }
    }

    return 0;
}

struct
{
    const char *name;
    int arg_num;
    int (*func)(char *args[]);
    char *short_cut;
} option_table[] = {
    {"add", 1, option_add, "a"},
    {"delete", 1, option_delete, "d"},
    {"host_encrypt", 1, option_host_encrypt, "he"},
    {"guest_encrypt", 2, option_guest_encrypt, "ge"},
    {"host_decrypt", 1, option_host_decrypt, "hd"},
    {"guest_decrypt", 2, option_guest_decrypt, "gd"},
    {"host_pipe_test", 1, option_host_pipe_test, "hpt"},
    {"guest_pipe_test", 2, option_guest_pipe_test, "gpt"},
};

#define OPTION_NUM (sizeof(option_table) / sizeof(option_table[0]))

int main(int argc, char *argv[])
{
    assert(ka_init() == 0);
    for (size_t i = 0; i < argc; i++)
    {
        printf("ARG [%s]\n", argv[i]);
        if (argv[i][0] != '-')
            continue;
        for (size_t j = 0; j < OPTION_NUM; j++)
        {
            if (strcmp(&argv[i][1], option_table[j].short_cut) == 0)
            {
                printf("execute command %s\n", option_table[j].name);
                int renamin_arg_num = argc - i - 1;
                if (renamin_arg_num < option_table[j].arg_num)
                {
                    printf("cmd %s arg not enough, need %d args\n", option_table[j].name, option_table[j].arg_num);
                    return -1;
                }
                int rc = option_table[j].func(&argv[i + 1]);
                if (rc)
                {
                    printf("command %s return %d\n", option_table[j].name, rc);
                    return rc;
                };
                i += option_table[j].arg_num;
                break;
            }
        }
    }
    ka_deinit();
    return 0;
}